package it.dk.model;

import java.util.ArrayList;

import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;

/**
 * Classe che si occupa della gestione della logica del gioco
 * e che contiene tutti gli elementi del gioco (statici e dinamici)
 */
public class Game {

	/* Numero dei possibili oggetti che possono collidere con Hero */
	private static final int OBJECTS_COLLISION = 10;
	/* Numero massimo di barili istanziabili */
	private static final int MAX_BARRELS = 10;
	
	/* Dimensioni finestra creata dalla view (Board) */
	private int boardHeight;
	private int boardWidth;
	
	/* Margini destro e sinistro della finestra di gioco */
	private WindowLeftBorder leftBorder;
	private WindowRightBorder rightBorder;
	
	/* Oggetti del gioco */
	private Hero hero;
	private Princess princess;
	private ArrayList<Barrel> barrels;
	private ArrayList<Platform> platforms;
	private ArrayList<Ladder> ladders;
	
	/* Dimensione immagine sorgente oggetti statici (di forma quadrata) */
	private int tileSize;
	private int heroWidth;
	private int heroHeight;
	private int heroInnerShapeWidth;
	private int heroInnerShapeHeight;
	private int barrelSize;
	private int princessHeight;
	private int princessWidth;
	private int lifesIconHeight;
	private int lifesIconWidth;
	
	/* Velocita' di riferimento lungo gli assi*/
	private float referenceSpeedX;
	private float referenceSpeedY;
	/* Velocita' barili (da settare all'atto dell'inizio del livello) */
	private float barrelSpeedX;
	private float barrelSpeedY;
	/* Riferimento alla gravita' degli oggetti */
	private float gravity;
	/* tempo in millisecondi dall'ultima aggiunta di un barile */
	private long lastBarrelGenerationTime;
	/* Indice del prossimo barile da attivare all'interno dell'array list di barili */
	private int nextBarrelActivatedIndex;
	
	/* Identificatore collisioni */
	private CollisionManager cm;
	
	/**
	 * Costruttore di classe
	 * 
	 * @param gameWidth				larghezza della finestra di gioco
	 * @param gameHeight			altezza della finestra di gioco
	 * @param dynamicObjectSpeedX	velocit� lungo l'asse x degli oggetti dinamici
	 * @param dynamicObjectSpeedY	velocit� lungo l'asse y degli oggetti dinamici
	 * @param gravity				gravit� a cui sono soggetti gli oggetti dinamici
	 * @throws SlickException		eccezione della libreria Slick2D
	 */
	public Game(int gameWidth, int gameHeight, float dynamicObjectSpeedX, float dynamicObjectSpeedY, float gravity, int level) throws SlickException{
		
		/* Istanzia gli oggetti (o le liste) del gioco */
		this.platforms = new ArrayList<Platform>();
		this.ladders = new ArrayList<Ladder>();
		this.barrels = new ArrayList<Barrel>();
		
		this.hero = Hero.getInstance();
		
		/* Determina l'altezza degli elementi statici in base alla grandezza della view in Board */
		this.tileSize = (int)Math.floor(gameHeight/28);
		this.heroHeight = (int)Math.floor(gameHeight/17);
		this.heroWidth = (int)Math.floor(heroHeight/1.17);
		this.heroInnerShapeHeight = (int)Math.floor(heroHeight*0.9);
		this.heroInnerShapeWidth = (int)Math.floor(heroWidth*0.55);
		this.barrelSize = (int)Math.floor(gameHeight/28);
		this.princessHeight = (int)Math.floor(gameHeight/10);
		this.princessWidth = (int)Math.floor(princessHeight/1.66);
		this.lifesIconWidth = (int)Math.floor(gameWidth/23);
		this.lifesIconHeight = (int)Math.floor(lifesIconWidth*0.71);
		
		/* Salva i valori di dimensione della view (da Board) */
		this.boardWidth = gameWidth;
		this.boardHeight = gameHeight;
		this.referenceSpeedX = dynamicObjectSpeedX;
		this.referenceSpeedY = dynamicObjectSpeedY;
		this.barrelSpeedX = dynamicObjectSpeedX+(level*0.05f);
		this.barrelSpeedY = dynamicObjectSpeedY;
		this.gravity = gravity;
		
		/* Inizializzo tutti i barili all'interno dell'array */
		for(int i=0; i<MAX_BARRELS; i++){
			barrels.add(new Barrel(new Rectangle(barrelSize, -barrelSize, barrelSize, barrelSize), barrelSpeedX, barrelSpeedY, gravity));
		}
		
		/* Creazione dei bordi sinistro e destro della finestra */
		this.leftBorder=new WindowLeftBorder(boardWidth,boardHeight);
		this.rightBorder=new WindowRightBorder(boardWidth,boardHeight);
		
		this.nextBarrelActivatedIndex = 0;
		
		/* Crea la disposizione degli oggetti all'interno della scena */
		setupLayer();
		
		/*Inizializzio l'identificatore delle collisioni */
		cm = new CollisionManager(this);
	}
	
	/**
	 * Metodo getter per il gestore delle collisioni
	 * 
	 * @return	gestore delle collisioni
	 */
	public CollisionManager getCollisionManager(){
		return cm;
	}

	/**
	 * Metodo getter per l'oggetto rappresentante l'eroe
	 * 
	 * @return	oggetto rappresentante l'eroe
	 */
	public Hero getHero(){
		return hero;
	}
	
	/**
	 * Metodo getter per l'oggetto rappresentante la principessa
	 * 
	 * @return	oggetto rappresentante la principessa
	 */
	public Princess getPrincess(){
		return princess;
	}

	/**
	 * Metodo getter per la collezione contenente i barili
	 * 
	 * @return	collezione contenente i barili
	 */
	public ArrayList<Barrel> getBarrels(){
		return barrels;
	}
	
	/**
	 * Metodo getter per la collezione contenente le piattaforme
	 * 
	 * @return	collezione contenente le piattaforme
	 */
	public ArrayList<Platform> getPlatforms(){
		return platforms;
	}
	
	/**
	 * Metodo getter per la collezione contenente le scale
	 * 
	 * @return	collezione contenente le scale
	 */
	public ArrayList<Ladder> getLadders(){
		return ladders;
	}

	/**
	 * Metodo getter per la larghezza della finestra di gioco
	 * 
	 * @return	larghezza della finestra di gioco
	 */
	public int getGameWidth(){
		return boardWidth;
	}

	/**
	 * Metodo getter per l'altezza della finestra di gioco
	 * 
	 * @return	altezza della finestra di gioco
	 */
	public int getGameHeight(){
		return boardHeight;
	}

	/**
	 * Metodo getter della dimensione dei blocchi statici elementari del gioco
	 * 
	 * @return	dimensione dei blocchi statici elementari del gioco
	 */
	public int getTileSize(){
		return tileSize;
	}
	
	/**
	 * Metodo getter per la dimensione dei barili
	 * 
	 * @return	la dimensione dei barili
	 */
	public int getBarrelSize(){
		return barrelSize;
	}
	
	/**
	 * Metodo getter per l'altezza della principessa
	 * 
	 * @return	l'altezza della principessa
	 */
	public int getPrincessHeight(){
		return princessHeight;
	}
	
	/**
	 * Metodo getter per la larghezza della principessa
	 * 
	 * @return	la larghezza della pricipessa
	 */
	public int getPrincessWidth(){
		return princessWidth;
	}
	
	/**
	 * Metodo getter dell'altezza dell'icona delle vite
	 * 
	 * @return	altezza dell'icona delle vite
	 */
	public int getLifesIconHeight(){
		return lifesIconHeight;
	}
	
	/**
	 * Metodo getter della larghezza dell'icona delle vite
	 * 
	 * @return	larghezza dell'icona delle vite
	 */
	public int getLifesIconWidth(){
		return lifesIconWidth;
	}
	
	/**
	 * Metodo getter per la larghezza dell'eroe
	 * 
	 * @return	la larghezza dell'eroe
	 */
	public int getHeroWidth(){
		return heroWidth;
	}
	
	/**
	 * Metodo getter per l'altezza dell'eroe
	 * 
	 * @return	l'altezza dell'eroe
	 */
	public int getHeroHeight(){
		return heroHeight;
	}
	
	/**
	 * Metodo getter per la larghezza della shape interna pi� piccola dell'eroe
	 * 
	 * @return	la larghezza della shape interna pi� piccola dell'eroe
	 */
	public int getHeroInnerShapeWidth(){
		return heroInnerShapeWidth;
	}
	
	/**
	 * Metodo getter per l'altezza della shape interna pi� piccola dell'eroe
	 * 
	 * @return	l'altezza della shape interna pi� piccola dell'eroe
	 */
	public int getHeroInnerShapeHeight(){
		return heroInnerShapeHeight;
	}
	
	/**
	 * Metodo setter della velocit� lungo l'asse x dei barili
	 * 
	 * @param speed	velocit� lungo l'asse x dei barili
	 */
	public void setBarrelSpeed(float speed){
		this.barrelSpeedX = speed;
	}
	
	/**
	 * Metodo getter per la velocit� lungo l'asse x dei barili
	 * 
	 * @param speed	velocit� lungo l'asse x dei barili
	 */
	public float getBarrelSpeed(){
		return barrelSpeedX;
	}
	
	/**
	 * Metodo getter dell'oggetto che rappresenta il bordo sinistro della schermata di gioco
	 * 
	 * @return	oggetto che rappresenta il bordo sinistro della schermata di gioco
	 */
	public WindowLeftBorder getLeftBorder() {
		return leftBorder;
	}

	/**
	 * Metodo getter dell'oggetto che rappresenta il bordo destro della schermata di gioco
	 * 
	 * @return	oggetto che rappresenta il bordo destro della schermata di gioco
	 */
	public WindowRightBorder getRightBorder() {
		return rightBorder;
	}
	
	/**
	 * Metodo getter del numero di possibili oggetti che possono collidere con hero
	 * 
	 * @return	numero di possibili oggetti che possono collidere con hero
	 */
	public int getObjectsCollision(){
		return OBJECTS_COLLISION;
	}
	
	/**
	 * Metodo getter del numero massimo di barili
	 * 
	 * @return	numero massimo di barili
	 */
	public int getMaxBarrels(){
		return MAX_BARRELS;
	}
	
	/**
	 * Metodo per l'aggiunta dinamica di un barile alla lista di barili
	 * (viene salvato il tempo in cui viene aggiunto uno nuovo)
	 */
	public void activateBarrel(){
		lastBarrelGenerationTime = System.currentTimeMillis();
		
		boolean isBarrelChosen = false;
		for(int i=0; i<barrels.size(); i++){
			if(!isBarrelChosen && !barrels.get(i).isActivated()){
				nextBarrelActivatedIndex = i;
				isBarrelChosen = true;
			}
		}
		
		barrels.get(nextBarrelActivatedIndex).setActivated(true);
	}
	
	/**
	 * Metodo per il riposizionamento di un barile all'atto
	 * dell'intersezione con il bordo sinistro della schermata
	 * di gioco (condizione di "uscita di scena del barile")
	 */
	public void resetBarrelPosition(int i){
		barrels.get(i).getShape().setX(barrelSize);
		barrels.get(i).getRound().setX(barrelSize);
		barrels.get(i).getUpperGate().setX(barrels.get(i).getShape().getX()+barrels.get(i).getShape().getWidth()/4);
		barrels.get(i).getShape().setY(-barrelSize);
		barrels.get(i).getRound().setY(-barrelSize);
		barrels.get(i).getUpperGate().setY(barrels.get(i).getShape().getY()-barrels.get(i).getShape().getHeight());
		
		barrels.get(i).setActivated(false);
	}	
	
	/**
	 * Metodo che restituisce il tempo in cui � stato generato l'ultimo barile
	 * 
	 * @return	tempo in cui � stato generato l'ultimo barile
	 */
	public long getLastBarrelGenerationTime(){
		return lastBarrelGenerationTime;
	}

	/**
	 * Metodo che si occupa del setup degli oggetti statici del livello di gioco
	 * 
	 * @throws SlickException	eccezione della libreria Slick2D
	 */
	public void setupLayer() throws SlickException{
		float shortFactor = 4 * tileSize;
		float shortWidth = boardWidth-shortFactor;
		
		platforms.add( new Platform(0,boardHeight-tileSize,
				boardWidth,tileSize));

		platforms.add( new Platform(0,boardHeight-5*tileSize,
				shortWidth,tileSize));

		platforms.add( new Platform(shortFactor, boardHeight-9*tileSize,
				shortWidth, tileSize));

		platforms.add(new Platform(0,boardHeight-13*tileSize,
				shortWidth,tileSize));

		platforms.add( new Platform(shortFactor,boardHeight-17*tileSize,
				shortWidth,tileSize));
		
		platforms.add( new Platform(0,boardHeight-21*tileSize,
				shortWidth,tileSize));
		
		platforms.add( new Platform(boardWidth/2 , boardHeight-25*tileSize,
				10*tileSize,tileSize));
		
		ladders.add(new Ladder((float) (platforms.get(1).getShape().getX()+ platforms.get(1).getShape().getWidth()*0.9), 
				platforms.get(1).getShape().getY(), 
				tileSize,4*tileSize));
		
		ladders.add(new Ladder((float) (platforms.get(2).getShape().getX()+platforms.get(2).getShape().getWidth()*0.2),
				platforms.get(2).getShape().getY(),
				tileSize,4*tileSize));

		ladders.add(new Ladder((float) (platforms.get(2).getShape().getX()+platforms.get(2).getShape().getWidth()*0.55),
				platforms.get(2).getShape().getY(),
				tileSize,4*tileSize));

		ladders.add(new Ladder((float) (platforms.get(3).getShape().getX()+platforms.get(3).getShape().getWidth()*0.40),
				platforms.get(3).getShape().getY(),
				tileSize,4*tileSize));

		ladders.add(new Ladder((float) (platforms.get(3).getShape().getX()+platforms.get(3).getShape().getWidth()*0.85),
				platforms.get(3).getShape().getY(),
				tileSize,4*tileSize));

		ladders.add(new Ladder((float) (platforms.get(4).getShape().getX()+platforms.get(4).getShape().getWidth()*0.1),
				platforms.get(4).getShape().getY(),
				tileSize,4*tileSize));

		ladders.add(new Ladder((float) (platforms.get(5).getShape().getX()+platforms.get(5).getShape().getWidth()*0.55),
				platforms.get(5).getShape().getY(),
				tileSize,4*tileSize));
		
		ladders.add(new Ladder((float) (platforms.get(6).getShape().getX()+platforms.get(6).getShape().getWidth()*0.5),
				platforms.get(6).getShape().getY(),
				tileSize,4*tileSize));

		princess = new Princess(new Rectangle(platforms.get(6).getShape().getX()+princessWidth/2,
				platforms.get(6).getShape().getY()-princessHeight,
				princessWidth,
				princessHeight));
		
		hero.init(new Rectangle(0,boardHeight-heroHeight-platforms.get(0).getShape().getHeight(),heroWidth,heroHeight), new Rectangle(5+(heroWidth/2)-(heroInnerShapeWidth/2),boardHeight-heroInnerShapeHeight-platforms.get(0).getShape().getHeight(),heroInnerShapeWidth,heroInnerShapeHeight), referenceSpeedX, referenceSpeedY, gravity);
	}
}
